/******************************************************************************************************
Copyright (C) 2017 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/
/*globals MnUi, FileDialog, settings, qsTr, File, BinaryStream, Application, nmr, NMRPredictorParams, NMRSpectrum, mainWindow, MessageBox, Dir*/
/*jslint plusplus: true, indent: 4*/

var DataTypes = {Int16 : 0, Int32 : 1, Real32 : 2, Real64 : 3};

function GePImporter() {
	"use strict";
	this.ui = 0;
	this.header_offset = 0;
	this.header = {
		revision : 0.0,
		frame_count : 0,
		frame_size : 0,
		bandwidth : 0.0,
		data_offset : 0,
		complex : true,
		skip_frames : 0,
		endian : BinaryStream.eLittle,
		file_size : 0
	};
}

function importGeP() {
	"use strict";
	var workingDir, fileName, importer;
	try {
		workingDir = settings.value(GePImporter.cRegKey + "lastDir", Dir.home());
		fileName = FileDialog.getOpenFileName(qsTr("GE P files (*.7)"), "Select GE P file", workingDir);
		if (!fileName.length) {
			return;
		}
		workingDir = File.absDirPath(fileName);
		settings.setValue(GePImporter.cRegKey + "lastDir", workingDir);

		importer = new GePImporter();
		importer.readHeader(fileName);
		importer.runParamsDialog();
		importer.readData(fileName);
	} catch (err) {
		mainWindow.endBusy();
		MessageBox.critical(err);
	}
}

GePImporter.header_sizes = {
	"7.0":39984, "8.0":60464, "9.0":61464, "10.0":65560, "11.0":66072, "14.0":135704, 
	"14.1":135704, "14.2":142356, "14.300":145908, "15.000":145908, "15.001":145908, 
	"16.000":145908, "20.001":145908, "20.002":145932, "20.003":145932, "20.004":146564, 
	"20.005":149788, "20.006":149788, "20.007":149788, "24.000":157276, "25.001":166172, 
	"25.002":166172, "25.003":167196, "25.004":216348, "26.000":163868, "26.001":164532, 
	"26.002":213684, "27.000":219828, "27.001":228020, "28.000":228020, "28.002":228020, 
	"28.003":228020, "28.004":228020
};

GePImporter.prototype = {};
GePImporter.cRegKey = "importGeP/";

GePImporter.prototype.readHeaderNum = function (aStream, aOffset, aDataType) {
	"use strict";
	var result;
	aStream.pos = aOffset;
	if (aStream.pos < 0) {
		throw qsTr("Error reading header");
	}
	switch (aDataType) {
	case DataTypes.Int16:
		result = aStream.readInt16();
		break;
	case DataTypes.Int32:
		result = aStream.readInt32();
		break;
	case DataTypes.Real32:
		result = aStream.readReal32();
		break;
	case DataTypes.Real64:
		result = aStream.readReal64();
		break;
	default:
		result = null;
	}
	return result;
};

GePImporter.prototype.headerOffset = function() {
	"use strict"
	var offset, rev = this.header.revision.toFixed(1);
	offset = GePImporter.header_sizes[rev];
	if (offset === undefined) {
		rev = this.header.revision.toFixed(3)
		offset = GePImporter.header_sizes[rev];
		if (offset === undefined) {
			return 0;
		}
	}
	return offset;
}

GePImporter.prototype.formatValid = function (aStream) {
	"use strict";
	var time, regex, ok;
	aStream.pos = 26;
	time = aStream.readBytes(8).toString();
	regex = /\d\d:\d\d/;
	ok = regex.test(time);
	if (!ok) {
		aStream.pos = 102;
		time = aStream.readBytes(8).toString();
		ok = regex.test(time);
		if (ok) {
			this.header_offset = 76; //header shifted by 76 bytes as found in #13338, format rev. 26
		}
	}
	return ok;
};

GePImporter.prototype.readHeader = function (fileName) {
	"use strict";
	var file, stream, data_size;
	file = new File(fileName);
	//Check file
	if (!file.open(File.ReadOnly)) {
		throw qsTr("The file could not be opened");
	}
	stream = new BinaryStream(file);
	stream.endianness = BinaryStream.eLittle;
	this.header.revision = this.readHeaderNum(stream, 0, DataTypes.Real32);

	if (this.header.revision < 7 || this.header.revision > 30) {
		stream.endianness = BinaryStream.eBig;
		this.header.revision = this.readHeaderNum(stream, 0, DataTypes.Real32);
	}
	this.header.endian = stream.endianness;

	if (!this.formatValid(stream)) {
		throw qsTr("Data Format is not recognized");
	}

	this.header.file_size = file.size;

	this.header.frame_count = this.readHeaderNum(stream, 74 + this.header_offset, DataTypes.Int16);

	this.header.frame_size = this.readHeaderNum(stream, 80 + this.header_offset, DataTypes.Int16);

	if (this.header.revision >= 26.0) {
		this.header.bandwidth = 2*this.readHeaderNum(stream, 432, DataTypes.Real32)/1000.0;
	} else {
		this.header.bandwidth = this.readHeaderNum(stream, 1644 + this.header_offset, DataTypes.Real32);
	}


	this.header.data_offset = this.headerOffset();
	if (this.header.data_offset<39984) {
		if (this.header.header_offset == 76) {
			this.header.data_offset = this.readHeaderNum(stream, 4, DataTypes.Int32);
		} else {
			this.header.data_offset = this.readHeaderNum(stream, 1468 + this.header_offset, DataTypes.Int32);
		}
	}

	if (this.header.revision <= 11) {
		this.header.complex = false;
	}

	if (this.header.data_offset < 39984) { //cannot read data offset, try to guess
		data_size = this.header.frame_size * this.header.frame_count * 4;
		if (this.header.complex) {
			data_size *= 2;
		}
		this.header.data_offset = this.header.file_size - data_size;
	}

	this.header.skip_frames = this.guessSkippedFrames(stream);
	file.close();
};

GePImporter.prototype.runParamsDialog = function () {
	"use strict";
	this.ui = Application.loadUiFile("ricares:importGeP.ui");
	if (!this.ui) {
		throw qsTr('Unable to load UI');
	}

	//load parameters
	this.ui.lbRevision.text = this.header.revision.toFixed(3);
	this.ui.sbFrameSize.value = this.header.frame_size;
	this.ui.sbFrameCount.value = this.header.frame_count;
	this.ui.dsbSpecwidth.value = this.header.bandwidth*1000.0;
	this.ui.cbComplex.checked = this.header.complex;
	this.ui.sbDataOffset.value = this.header.data_offset;
	this.ui.sbSkipFrames.value = this.header.skip_frames;
	this.ui.lbSizeWarning.visible = false;

	this.ui.sbFrameSize['valueChanged(int)'].connect(this, 'checkSize');
	this.ui.sbFrameCount['valueChanged(int)'].connect(this, 'checkSize');
	this.ui.sbDataOffset['valueChanged(int)'].connect(this, 'checkSize');
	this.ui.sbSkipFrames['valueChanged(int)'].connect(this, 'checkSize');
	this.ui.cbComplex['stateChanged(int)'].connect(this, 'checkSize');

	if (!this.ui.exec()) {
		throw qsTr("Interrupted by user");
	}

	//update parameters
	this.header.frame_size = this.ui.sbFrameSize.value;
	this.header.frame_count = this.ui.sbFrameCount.value;
	this.header.complex = this.ui.cbComplex.checked;
	this.header.data_offset = this.ui.sbDataOffset.value;
	this.header.skip_frames = this.ui.sbSkipFrames.value;
	this.header.bandwidth = this.ui.dsbSpecwidth.value/1000.0;
};

GePImporter.prototype.checkSize = function () {
	"use strict";
	var size, shift, fsize;
	this.ui.lbSizeWarning.text = "<font color=\"red\"> File size of " + this.header.file_size.toFixed(0) + "<br>is uncompatible with parameters</font>";
	this.ui.lbSizeWarning.visible = false;
	size = this.ui.sbFrameSize.value * this.ui.sbFrameCount.value * 4;
	if (this.ui.cbComplex.checked) {
		size *= 2;
	}
	shift = this.ui.sbSkipFrames.value * this.ui.sbFrameSize.value;
	if (this.ui.cbComplex.checked) {
		shift *= 2;
	}
	shift += this.ui.sbDataOffset.value;
	fsize = shift + size;
	if (shift + size > this.header.file_size) {
		this.ui.lbSizeWarning.visible = true;
	}
};

GePImporter.prototype.guessSkippedFrames = function (aStream) {
	"use strict";
	var curVal, moved, fid_bytes, skip;
	skip = 0;
	aStream.pos = this.header.data_offset;
	if (aStream.pos < 0) {
		throw qsTr("Data offset is bigger than the file size!");
	}
	curVal = aStream.readInt32();
	while ((!aStream.atEnd()) && curVal === 0) {
		curVal = aStream.readInt32();
	}
	moved =  aStream.pos - 4 - this.header.data_offset;
	if (moved > 0) {
		fid_bytes = this.header.frame_size * 4;
		if (this.header.complex) {
			fid_bytes *= 2;
		}
		if (moved % fid_bytes === 0) {
			skip = moved / fid_bytes;
		}
	}
	return skip;
};

GePImporter.prototype.readData = function (fileName) {
	"use strict";
	var file, stream, shift_bytes, fid, result, i, j, proc;
	file = new File(fileName);
	//Check file
	if (!file.open(File.ReadOnly)) {
		throw qsTr("The file could not be opened");
	}
	stream = new BinaryStream(file);
	stream.endianness = this.header.endian;
	shift_bytes = this.header.skip_frames * this.header.frame_size * 4;
	if (this.header.complex) {
		shift_bytes *= 2;
	}
	stream.pos = this.header.data_offset + shift_bytes;

	result = this.createSpectrum(this.header.frame_size, this.header.frame_count);
	nmr.beginFidModification(result.spectrum);

	mainWindow.setBusy(qsTr("Loading data. Please wait..."));

	for (i = 0; i < this.header.frame_count; i++) {
	    fid = {real: [], imag: []};
		for (j = 0; j < this.header.frame_size; j++) {
			fid.real.push(stream.readInt32());
			if (this.header.complex) {
				fid.imag.push(stream.readInt32());
			} else {
				fid.imag.push(0);
			}
		}
		if (this.header.frame_count <= 1) {
			result.spectrum.fidSetReal({from: 0, to: (fid.real.length - 1)}, fid.real);
			result.spectrum.fidSetImag({from: 0, to: (fid.imag.length - 1)}, fid.imag);
		} else {
			result.spectrum.fidSetReal(i, {from: 0, to: (fid.real.length - 1)}, fid.real);
			result.spectrum.fidSetImag(i, {from: 0, to: (fid.imag.length - 1)}, fid.imag);
		}
	}
	nmr.endFidModification(result.spectrum);
	mainWindow.doAction("nmr2DTo1DStack");
	proc = result.spectrum.proc;
	proc.setParameter("FT[1].Quadrature", true);
	proc.setParameter("FT[1].Invert", true);
	result.spectrum.proc = proc;
	result.spectrum.process();
	mainWindow.endBusy();
	result.spectrum.update();
	file.close();
};

GePImporter.prototype.createSpectrum = function (af2Size, af1Size) {
	"use strict";
	var result, f2Spectrum, f2Peaks, f1Spectrum, f1Peaks, spectrum;

	result = {};

	f2Spectrum = new NMRPredictorParams("1H");
	f2Spectrum.frequency = 63.87;
	f2Spectrum.fromPPM = -1 * this.header.bandwidth / 2.0 * 1000.0 / f2Spectrum.frequency;
	f2Spectrum.toPPM = this.header.bandwidth / 2.0 * 1000.0 / f2Spectrum.frequency;
	f2Spectrum.size = af2Size;
	f2Peaks = [];

	f1Spectrum = new NMRPredictorParams("1H");
	f1Spectrum.frequency = 63.87;
	f1Spectrum.fromPPM = 0;
	f1Spectrum.toPPM = 100;
	f1Spectrum.size = af1Size;
	f1Peaks = [];

	if (af2Size > 1) {
		spectrum = new NMRSpectrum(f2Spectrum, f2Peaks, f1Spectrum, f1Peaks, true);
	} else {
		spectrum = new NMRSpectrum(f2Spectrum, f2Peaks, true);
	}
	if (spectrum.uuid === undefined) {
		throw qsTr("The spectrum creation process has failed");
	} else {
		result.spectrum = spectrum;
		spectrum.update();
	}

	return result;
};

if (this.MnUi && MnUi.scripts_nmr) {
	MnUi.scripts_nmr.scripts_nmr_ImportGeP = importGeP;
}
